package com.ciccwm.log.aspect;

import com.ciccwm.log.annotation.LogPrint;
import com.ciccwm.log.constant.LogConstant;
import com.ciccwm.log.handler.LogHandler;
import com.ciccwm.log.model.InvokeContext;
import com.ciccwm.log.model.LogRecord;
import com.ciccwm.log.util.RequestContextUtil;
import com.ciccwm.log.util.TraceIdUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Optional;

/**
 * 日志打印切面
 * 用于拦截带有@LogPrint注解的方法，记录入参和出参
 */
@Slf4j
@Aspect
@RequiredArgsConstructor
public class LogPrintAspect {

    private final LogHandler logHandler;

    @Around("@annotation(logPrint) || @within(logPrint)")
    public Object around(ProceedingJoinPoint joinPoint, LogPrint logPrint) throws Throwable {
        Optional<LogPrint> optionalLogPrint = getAnnotation(joinPoint, logPrint);
        // 获取注解
        if (optionalLogPrint.isPresent()) {
            // 记录请求开始时间
            InvokeContext invokeContext = getInvokeContext(joinPoint);

            // 执行目标方法并记录结果
            invokeTargetMethod(joinPoint, invokeContext);

            // 构建并记录日志
            LogRecord record = convertToLogRecord(invokeContext);
            logHandler.handle(joinPoint, optionalLogPrint.get().mask(), record);

            // 如果有异常，重新抛出
            if (invokeContext.isException()) {
                throw invokeContext.getThrowable();
            }
            return invokeContext.getRspBody();
        } else {
            return joinPoint.proceed();
        }
    }

    private static InvokeContext getInvokeContext(ProceedingJoinPoint joinPoint) {
        return InvokeContext.builder()
                .reqId(TraceIdUtil.getTraceId())
                .reqTime(LocalDateTime.now())
                .reqDate(LocalDate.now())
                .reqHeader(RequestContextUtil.getHeaders())
                .reqBody(joinPoint.getArgs()).build();
    }

    /**
     * 获取注解
     */
    private Optional<LogPrint> getAnnotation(ProceedingJoinPoint joinPoint, LogPrint logPrint) {
        try {
            if (logPrint != null) {
                return Optional.of(logPrint);
            } else {
                MethodSignature signature = (MethodSignature) joinPoint.getSignature();
                return Optional.ofNullable(signature.getMethod().getAnnotation(LogPrint.class));
            }
        } catch (Exception e) {
            log.error("Unexpected error while getting annotation from method", e);
        }
        return Optional.empty();
    }

    /**
     * 执行目标方法
     */
    private void invokeTargetMethod(ProceedingJoinPoint joinPoint, InvokeContext invokeContext) {
        try {
            invokeContext.setRspBody(joinPoint.proceed());
            invokeContext.setRspTime(LocalDateTime.now());
            invokeContext.setRspCode(LogConstant.SUCCESS);
            invokeContext.setRspMsg(LogConstant.SUCCESS_MSG);
        } catch (Throwable throwable) {
            invokeContext.setThrowable(throwable);
            invokeContext.setRspTime(LocalDateTime.now());
            invokeContext.setRspCode(LogConstant.ERROR);
            invokeContext.setRspMsg(throwable.getMessage());
        }
    }

    /**
     * 构建日志记录
     */
    private LogRecord convertToLogRecord(InvokeContext invokeContext) {
        return LogRecord.builder().reqId(TraceIdUtil.getTraceId())
                .reqDate(invokeContext.getReqDate())
                .reqTime(invokeContext.getReqTime())
                .reqHeader(invokeContext.getReqHeader())
                .reqBody(invokeContext.getReqBody())
                .rspTime(invokeContext.getRspTime())
                .rspHeader(Collections.emptyMap())
                .rspBody(invokeContext.getRspBody())
                .rspCode(invokeContext.getRspCode())
                .rspMsg(invokeContext.getRspMsg())
                .costTime(getCostTime(invokeContext)).build();
    }

    private static long getCostTime(InvokeContext invokeContext) {
        return invokeContext.getRspTime() != null ? Duration.between(invokeContext.getReqTime(),
                invokeContext.getRspTime()).toMillis() : 0;
    }
} 